#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media.Animation;
using UTable.Config;
using UTable.Input;
using UTable.Input.MultiTouch;
using UTable.Input.Text;
using UTable.ObjectManager;
using UTable.ObjectManager.Commands;
using UTable.Objects.Controls;

namespace UTable.Objects
{
    public class UTableHelper
    {
		#region Configuration variables

        internal static readonly bool DevelopMode = true;

        // internal static readonly bool EnableBitmapEffect = false;

		#endregion

        private UTableHelper()
        {
        }
        
        private static object lockReference = new object();

        private static int _id = 0;

        private static Dictionary<int, IVisualTree> _visualTreeMap = new Dictionary<int, IVisualTree>();

        private static Dictionary<int, IInputManager> _inputManagerMap = new Dictionary<int, IInputManager>();

        private static Dictionary<Type, Collection<Type>> _inputMappers = new Dictionary<Type, Collection<Type>>();

		private static ShareCenter _shareCenter = new ShareCenter();

        private static IGlobalObjectManager _globalObjectManager;

        private static TextInputManager _textInputManager;

        private static int _threadId;

        #region Internal Methods

        internal static void RegistgerGlobalObjectManager(IGlobalObjectManager manager)
        {
            _globalObjectManager = manager;
            _threadId = Thread.CurrentThread.ManagedThreadId;
        }

        internal static void RegisterTextInputManager(TextInputManager manager)
        {
            _textInputManager = manager;
        }

        internal static void RegisterVisualTree(IVisualTree visualTree)
        {
            int threadID = Thread.CurrentThread.ManagedThreadId;
            lock (_visualTreeMap)
            {
                if (!_visualTreeMap.ContainsKey(threadID))
                    _visualTreeMap.Add(threadID, visualTree);
            }
        }

        internal static void UnregisterVisualTree()
        {
            int threadID = Thread.CurrentThread.ManagedThreadId;
            lock (_visualTreeMap)
            {
                if (_visualTreeMap.ContainsKey(threadID))
                    _visualTreeMap.Remove(threadID);
            }
        }

        internal static void RegisterInputManager(IInputManager inputManager)
        {
            int threadID = Thread.CurrentThread.ManagedThreadId;
            lock (_inputManagerMap)
            {
                if (!_inputManagerMap.ContainsKey(threadID))
                    _inputManagerMap.Add(threadID, inputManager);
            }
            lock (_inputMappers)
            {
                foreach (Type t in _inputMappers.Keys)
                {
                    foreach (Type mapper in _inputMappers[t])
                        inputManager.RegisterInputMapper(t, Activator.CreateInstance(mapper) as IInputMapper);
                }
            }
        }

        internal static void UnregisterInputManager()
        {
            int threadID = Thread.CurrentThread.ManagedThreadId;
            lock (_inputManagerMap)
            {
                if (_inputManagerMap.ContainsKey(threadID))
                    _inputManagerMap.Remove(threadID);
            }
        }

        internal static IInputManager CurrentInputManager
        {
            get
            {
                IInputManager inputManger = null;
                int threadID = Thread.CurrentThread.ManagedThreadId;
                lock (_inputManagerMap)
                {
                    if (_inputManagerMap.ContainsKey(threadID))
                    {
                        inputManger = _inputManagerMap[threadID];
                    }
                }
                return inputManger;
            }
        }

        internal static IInputManager GlobalInputManager
        {
            get 
            {
                // now just return the global input manager
                return _inputManagerMap[_threadId];
            }
		}
		#endregion

		#region Public Methods

        /// <summary>
        /// Get the visual tree of current thread
        /// </summary>
		public static IVisualTree CurrentVisualTree
        {
            get
            {
                IVisualTree visualTree = null;
                int threadID = Thread.CurrentThread.ManagedThreadId;
                lock(_visualTreeMap)
                {
                    if (_visualTreeMap.ContainsKey(threadID))
                    {
                        visualTree = _visualTreeMap[threadID];
                    }
                }
                return visualTree;
            }
		}

        /// <summary>
        /// Get the UObject instance of current thread
        /// </summary>
        public static UObject CurrentObject
        {
            get
            {
                IVisualTree visualTree = CurrentVisualTree;
                if (visualTree != null && visualTree is UObject)
                    return visualTree as UObject;
                return null;
            }
        }

		public static ShareCenter ShareCenter
		{
			get { return _shareCenter; }
		}

        public static System.Windows.Media.Brush OpacityBrush
        {
            get
            {
                System.Windows.Media.Color c = new System.Windows.Media.Color();
                c.A = 1;
                return new System.Windows.Media.SolidColorBrush(c);
            }
        }

		public static void PutCommand(ICommand cmd)
        {
            List<ICommand> cmds = new List<ICommand>();
            cmds.Add(cmd);
            PutCommands(cmds);
        }

        internal static void PutCommands(List<ICommand> cmds)
        {
            if (Thread.CurrentThread.ManagedThreadId == _threadId)
            {
                _globalObjectManager.PutCommands(cmds);
            }
            else
            {
                _globalObjectManager.Dispatcher.BeginInvoke(
               new GlobalObjectManagerImpl.PutCommandsHandler(_globalObjectManager.PutCommands), new object[] { cmds });
            }
        }

        internal static void RegisterInputProvider(Type inputEventType, IInputProvider inputProvider)
        {
            if (inputEventType != typeof(InputEventArgs) && !inputEventType.IsSubclassOf(typeof(InputEventArgs)))
            {
                throw new ArgumentException("The inputEventType must inherit from InputEventArgs.");
            }
            lock(CurrentInputManager)
            {
                CurrentInputManager.RegisterInputProvider(inputEventType, inputProvider);
            }
        }

        internal static void UnregisterInputProvider(Type inputEventType, IInputProvider inputProvider)
        {
            if (!inputEventType.IsSubclassOf(typeof(InputEventArgs)))
            {
                throw new ArgumentException("The inputEventType must inherit from InputEventArgs.");
            }
            lock(CurrentInputManager)
            {
                CurrentInputManager.UnregisterInputProvider(inputEventType, inputProvider);
            }
        }

        internal static void RegisterInputMapper(Type inputEventType, IInputMapper inputMapper)
        {
            if (!inputEventType.IsSubclassOf(typeof(InputEventArgs)))
            {
                throw new ArgumentException("The inputEventType must inherit from InputEventArgs.");
            }
            //TODO inputMapper seems just be used as a type, change it to Type but not IInputMapper
            Type inputMapperType = inputMapper.GetType();
            lock (_inputManagerMap)
            {
                foreach (IInputManager manager in _inputManagerMap.Values)
                {
                    manager.RegisterInputMapper(inputEventType, Activator.CreateInstance(inputMapperType) as IInputMapper);
                }
            }
            lock (_inputMappers)
            {
                if (_inputMappers.ContainsKey(inputEventType))
                {
                    _inputMappers[inputEventType].Add(inputMapperType);
                }
                else
                {
                    Collection<Type> mappers = new Collection<Type>();
                    mappers.Add(inputMapperType);
                    _inputMappers.Add(inputEventType, mappers);
                }
            }
        }

        internal static void UnregisterInputMapper(Type inputEventType, IInputMapper inputMapper)
        {
            if (!inputEventType.IsSubclassOf(typeof(InputEventArgs)))
            {
                throw new ArgumentException("The inputEventType must inherit from InputEventArgs.");
            }
            // TODO
            lock (_inputManagerMap)
            {
                foreach (IInputManager manager in _inputManagerMap.Values)
                {
                    manager.UnregisterInputMapper(inputEventType, inputMapper);
                }
            }
            Type inputMapperType = inputMapper.GetType();
            lock (_inputMappers)
            {
                if (_inputMappers.ContainsKey(inputEventType))
                {
                    _inputMappers[inputEventType].Remove(inputMapperType);
                }
            }
        }

        internal static bool CaptureFinger(IVisualNode node, int fingerId, bool fromGlobal)
        {
            Collection<IInputMapper> mappers = CurrentInputManager.GetInputMappersByType(typeof(MultiTouchEventArgs));
            foreach (IInputMapper mapper in mappers)
            {
                if (mapper is MultiTouchInputMapper)
                {
                    if (Thread.CurrentThread.ManagedThreadId != _threadId)
                    {
                        bool success = (bool)_globalObjectManager.Dispatcher.Invoke(
                            new CaptureFingerHandler(CaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId, fromGlobal });
                        if (success)
                        {
                            success = (mapper as MultiTouchInputMapper).CaptureFinger(node, fingerId, fromGlobal);
                            if (!success)
                            {
                                _globalObjectManager.Dispatcher.Invoke(
                                    new UncaptureFingerHandler(UncaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId });
                                return false;
                            }
                            else
                                return true;
                        }
                        else
                            return false;
                    }
                    else
                    {
                        return (mapper as MultiTouchInputMapper).CaptureFinger(node, fingerId, fromGlobal);
                    }
                }
            }
            return false;
        }

        internal static bool UncaptureFinger(IVisualNode node, int fingerId)
        {
            Collection<IInputMapper> mappers = CurrentInputManager.GetInputMappersByType(typeof(MultiTouchEventArgs));
            foreach (IInputMapper mapper in mappers)
            {
                if (mapper is MultiTouchInputMapper)
                {
                    if (Thread.CurrentThread.ManagedThreadId != _threadId)
                    {

                        bool success = (bool)_globalObjectManager.Dispatcher.Invoke(
                            new UncaptureFingerHandler(UncaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId });
                        if (success)
                        {
                            return (mapper as MultiTouchInputMapper).UncaptureFinger(node, fingerId);
                        }
                        else
                            return false;
                    }
                    else
                    {
                        return (mapper as MultiTouchInputMapper).UncaptureFinger(node, fingerId);
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Capture the finger in the second way.
        /// In this way, all the events caused by the specified finger will send to the visual tree rooted by the specified node.
        ///     - If the inputs are initially mapped on this visual tree, then the inputs are mapped in the original way.
        ///     - If the inputs are not on the visual tree, the all the inputs will be sent to the specified node
        /// </summary>
        /// <param name="node">The node that will capture the finger</param>
        /// <param name="fingerId">The finger that is captured</param>
        /// <returns>true if captured successfully, otherwise false</returns>
        public static bool CaptureFinger2(IVisualNode node, int fingerId)
        {
            Collection<IInputMapper> mappers = CurrentInputManager.GetInputMappersByType(typeof(MultiTouchEventArgs));
            foreach (IInputMapper mapper in mappers)
            {
                if (mapper is MultiTouchInputMapper)
                {
					if (InGlobalScope())
					{
						return (mapper as MultiTouchInputMapper).CaptureFinger2(node, fingerId);
					}
					else
					{
						bool success = (bool)_globalObjectManager.Dispatcher.Invoke(
							new CaptureFingerHandler(CaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId, false });
						if (success)
						{
							success = (mapper as MultiTouchInputMapper).CaptureFinger2(node, fingerId);
							if (!success)
							{
								_globalObjectManager.Dispatcher.Invoke(
									new UncaptureFingerHandler(UncaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId });
								return false;
							}
							else
								return true;
						}
						else
							return false;
					}
                }
            }
            return false;
        }

        /// <summary>
        /// Release the finger which is captured by UTableHelper.CaptureFinger2
        /// </summary>
        /// <param name="node">The node that will capture the finger</param>
        /// <param name="fingerId">The finger that is captured</param>
        /// <returns>true if uncaptured successfully, otherwise false</returns>
        public static bool UncaptureFinger2(IVisualNode node, int fingerId)
        {
            Collection<IInputMapper> mappers = CurrentInputManager.GetInputMappersByType(typeof(MultiTouchEventArgs));
            foreach (IInputMapper mapper in mappers)
            {
                if (mapper is MultiTouchInputMapper)
                {
					if (InGlobalScope())
					{
						return (mapper as MultiTouchInputMapper).UncaptureFinger2(node, fingerId);
					}
					else
					{
						bool success = (bool)_globalObjectManager.Dispatcher.Invoke(
							new UncaptureFingerHandler(UncaptureFinger), new object[] { (CurrentVisualTree as UObject).Entity, fingerId });
						if (success)
						{
							return (mapper as MultiTouchInputMapper).UncaptureFinger2(node, fingerId);
						}
						else
							return false;
					}
                }
            }
            return false;
        }

        internal static String GetNewId()
        {
            lock (lockReference)
            {
                _id++;
                return _id.ToString();
            }
        }

		internal static IObjectEntity GetEntity(IVisualNode node)
		{
			if (node is IObjectEntity)
				return node as IObjectEntity;
			if (node is IControl)
				return (node as IControl).Info.Entity;
			return null;
		}

		internal static bool InGlobalScope()
		{
			return Thread.CurrentThread.ManagedThreadId == _threadId;
		}

        /// <summary>
        /// Create an object
        /// </summary>
        /// <param name="parameter">the parameter for creating the object</param>
        /// <returns>the handle of the created object</returns>
        public static IObject CreateObject(ObjectCreateParameter parameter)
        {
            if (!parameter.ObjectType.IsSubclassOf(typeof(UObject)))
                return null;
			if (InGlobalScope())
            {
                return _globalObjectManager.CreateObject(parameter);
            }
            else
            {
                IObject obj = (IObject)_globalObjectManager.Dispatcher.Invoke(
                    new GlobalObjectManagerImpl.CreateObjectHandler2(_globalObjectManager.CreateObject),
                    new object[] { parameter });
                if (obj is UObjectEntity)
                    return new UObjectProxy(obj as UObjectEntity);
                return null;
            }
        }

        public static IObject CreateObject(ObjectCreateParameter parameter, IObject owner)
        {
            IObject obj = UTableHelper.CreateObject(parameter);
            obj.Owner = owner;
            return obj;
        }

        public static IObject CreateObject(ObjectCreateParameter parameter, IObject owner, object initModel)
        {
            IObject obj = UTableHelper.CreateObject(parameter);
            obj.Owner = owner;
            obj.SendMessage(new InitializeModelMessage(initModel));
            return obj;
        }

        public static IObject CreateObject(Type objectType, IObject owner)
        {
            ObjectCreateParameter param = new ObjectCreateParameter(objectType);
            IObject obj = UTableHelper.CreateObject(param);
            obj.Owner = owner;
            return obj;
        }

        public static IObject CreateObject(Type objectType, IObject owner, object initModel)
        {
            ObjectCreateParameter param = new ObjectCreateParameter(objectType);
            IObject obj = UTableHelper.CreateObject(param);
            obj.Owner = owner;
            obj.SendMessage(new InitializeModelMessage(initModel));
            return obj;
        }

        /// <summary>
        /// Create and show the object with specified type, and put it within the current object
        /// The current object will wait until this new object is closed.
        /// </summary>
        /// <param name="objectType">The type of the object that will be created</param>
        /// <param name="objectModel">
        ///     The initial model that will be set to the new object.
        ///     After note that this model will be modified by the new object to reflect the changes,
        ///     so the caller can get the new data from this model
        ///  </param>
        public static void ShowModalObject(Type objectType, object objectModel)
        {
            IObject owner = CurrentVisualTree as IObject;
            if (owner == null)
                return;

            IObject obj = CreateObject(objectType, owner);
            obj.SendMessage(new InitializeModelMessage(objectModel));
            try
            {
                _threadMap[obj.ID].Join();
            }
            catch (System.Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            // return obj.QueryData(new QueryModelMessage());
        }

        internal static bool IsObjectWaiting(IObject obj)
        {
            // it costs too much
            // lock (_threadMap)
            {
                if (_threadMap.ContainsKey(obj.ID))
                {
                    return _threadMap[obj.ID].ThreadState != ThreadState.Background;
                }
                return true;
            }
        }

        internal static void RegisterObject(IObject obj)
        {
            lock (_threadMap)
            {
                _threadMap.Add(obj.ID, Thread.CurrentThread);
            }
        }

        internal static void UnregisterObject(IObject obj)
        {
            lock (_threadMap)
            {
                if (_threadMap.ContainsKey(obj.ID))
                    _threadMap.Remove(obj.ID);
            }
        }

        private static Dictionary<String, Thread> _threadMap = new Dictionary<string, Thread>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="parameter"></param>
        /// <returns></returns>
        internal static IObject CreateObject(IObject owner,  ObjectCreateParameter parameter)
        {
            if (!parameter.ObjectType.IsSubclassOf(typeof(UObject)))
                return null;
            if (owner is UObject)
            {
                owner = (owner as UObject).Entity;
            }
            if (owner is UObjectProxy)
            {
                owner = (owner as UObjectProxy).Entity;
            }
            if (Thread.CurrentThread.ManagedThreadId == _threadId)
            {
                return _globalObjectManager.CreateObject(owner, parameter);
            }
            else
            {
                IObject obj = (IObject)_globalObjectManager.Dispatcher.Invoke(
                    new GlobalObjectManagerImpl.CreateObjectHandler(_globalObjectManager.CreateObject), 
                    new object[] { owner, parameter});
                if (obj is UObjectEntity)
                    return new UObjectProxy(obj as UObjectEntity);
                return null;
                
            }
        } 

        public static void AddInputHook(IHook hook)
        {

        }

        public static void RemoveInputHook(IHook hook)
        {

        }

        private delegate void SetPolicyHandler(IObjectEntity obj, Type layoutPolicyType, object param);
        private delegate void UpdatePolicyHandler(IObjectEntity obj, object param);

        internal static void SetLayoutPolicy(IObject obj, Type layoutPolicyType, object parameter)
        {
            if (obj is UObject)
            {
                _globalObjectManager.Dispatcher.Invoke(
                    new SetPolicyHandler(_globalObjectManager.SetLayoutPolicy), 
                    new object[] { (obj as UObject).Entity, layoutPolicyType, parameter });
            }
        }
        internal static void UpdateLayoutPolicy(IObject obj, object parameter)
        {
            if (obj is UObject)
            {
                _globalObjectManager.Dispatcher.Invoke(
                    new UpdatePolicyHandler(_globalObjectManager.UpdateLayoutPolicy), 
                    new object[] {(obj as UObject).Entity, parameter});
            }
        }

        // not open to the users currently
        internal static void SetAuthorizationPolicy(IObject obj, Type authorizationPolicyType, object parameter)
        {
            if (obj is UObject)
            {
                _globalObjectManager.Dispatcher.Invoke(
                    new SetPolicyHandler(_globalObjectManager.SetAuthorizationPolicy),
                    new object[] { (obj as UObject).Entity, authorizationPolicyType, parameter });
            }
        }

        // not open to the users currently
        internal static void SetConflictResolvePolicy(IObject obj, Type conflictResolvePolicyType, object parameter)
        {
            if (obj is UObject)
            {
                _globalObjectManager.Dispatcher.Invoke(
                    new SetPolicyHandler(_globalObjectManager.SetConflictResolvePolicy),
                    new object[] { (obj as UObject).Entity, conflictResolvePolicyType, parameter });
            }
        }

        public static TextInputManager TextInputManager 
        {
            get { return _textInputManager; }
        }

        public static UTableConfig Config { get; internal set; }

        /// <summary>
        /// Shut down the application
        /// </summary>
        /// <param name="exitCode">Shuts down an application that returns
        /// the specified exit code to the operating system.</param>
        public static void Shutdown(int exitCode)
        {
            PutCommand(new SystemCommand(SystemCommandType.Shutdown, exitCode));
        }

        #region Animations

        public static AnimationTimeline CreateThicknessAnimation(Thickness to, TimeSpan duration)
        {
            SplineThicknessKeyFrame endKeyframe = new SplineThicknessKeyFrame();
            endKeyframe.Value = to;
            endKeyframe.KeyTime = KeyTime.FromTimeSpan(duration);

            KeySpline ks = new KeySpline();
            ks.ControlPoint1 = new Point(0, 1);
            ks.ControlPoint2 = new Point(1, 1);
            endKeyframe.KeySpline = ks;

            // Create Animation
            ThicknessAnimationUsingKeyFrames animation = new ThicknessAnimationUsingKeyFrames();
            animation.KeyFrames.Add(endKeyframe);
            return animation;
        }

        public static AnimationTimeline CreateDoubleAnimation(double to, TimeSpan duration)
        {
            SplineDoubleKeyFrame endKeyframe = new SplineDoubleKeyFrame();
            endKeyframe.Value = to;
            endKeyframe.KeyTime = KeyTime.FromTimeSpan(duration);

            KeySpline ks = new KeySpline();
            ks.ControlPoint1 = new Point(0, 1);
            ks.ControlPoint2 = new Point(1, 1);
            endKeyframe.KeySpline = ks;

            // Create Animation
            DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
            animation.KeyFrames.Add(endKeyframe);
            return animation;
        }

        #endregion

        #region Mathmatics

        /// <summary>
        /// Rotate and scale a point relative to the center
        /// </summary>
        public static Point RotateAndScalePoint(Point target, Point center, double angle, double zoom)
        {
            if (target == center)
                return target;

            Vector vector = target - center;
            double length = vector.Length * zoom;
            double a = CalcClockwiseAngle(vector, new Vector(1, 0)) - angle;
            a = (a + 360) % 360;
            double y = Math.Sin(a * Math.PI / 180);
            double x = Math.Sqrt(1 - y * y);
            if (a > 90 && a < 270)
                x *= -1;
            Vector v = new Vector(x * length, y * length);
            return center + v;
        }

        /// <summary>
        /// Calculate the angle to rotate v1 to v2 clockwisely
        /// the result is from 0 to 360
        /// </summary>
        public static double CalcClockwiseAngle(Vector v1, Vector v2)
        {
            v1.Normalize();
            v2.Normalize();
            double angle = Math.Asin(v2.X * v1.Y - v1.X * v2.Y) / Math.PI * 180;
            double innerProduct = v1.X * v2.X + v1.Y * v2.Y;
            if (innerProduct < 0)
            {
                if (angle > 0)
                    angle = 180 - angle;
                else
                    angle = -180 - angle;
            }
            if (angle < 0)
                angle += 360;
            return angle;
        }


        /// <summary>
        /// Converts a coordinate from the polar coordinate system to the cartesian coordinate system.
        /// </summary>
        /// <param name="angle"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        public static Point ComputeCartesianCoordinate(double angle, double radius)
        {
            // convert to radians
            double angleRad = (Math.PI / 180.0) * (angle - 90);

            double x = radius * Math.Cos(angleRad);
            double y = radius * Math.Sin(angleRad);

            return new Point(x, y);
        }

        #endregion

        #endregion
    }
}
